package com.icee.myth.common.encoder;

import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.MessageLite;
import com.icee.myth.common.protobufMessage.ProtobufMessage;
import com.icee.myth.utils.LogConsts;
import com.icee.myth.utils.MLogger;
import com.icee.myth.utils.StackTraceUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

import java.io.IOException;
import java.nio.ByteOrder;

import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
import static io.netty.buffer.Unpooled.wrappedBuffer;

/**
 * encode函数里默认使用的是Type2BytesLengthFieldProtobufEncoder方式编码
 * Created by guozhicheng on 2016/10/25.
 */
public class AbsProtobufEncoder extends MessageToByteEncoder {

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        if (null == msg) {
            throw new Exception("Type2BytesLengthFieldProtobufEncoder msg is null");
        }

        if (msg instanceof ProtobufMessage) {
            // Type2BytesLengthFieldProtobufEncoder
            ByteBuf bf = encodeProtobufMessage(ctx.channel(), (ProtobufMessage) msg);
            if (null != bf && !bf.equals(EMPTY_BUFFER)) {
                out.writeBytes(bf);
            } else {
                throw new Exception("Type2BytesLengthFieldProtobufEncoder bf is null or EMPTY_BUFFER");
            }
        } else {
            throw new Exception("Type2BytesLengthFieldProtobufEncoder msg unknow");
        }
    }

    /**
     * Type2BytesLengthFieldProtobufEncoder
     */
    protected ByteBuf encodeProtobufMessage(Channel channel, ProtobufMessage pm) {
        if (pm.payload == null) {
            ByteBuf header = channel.config().getAllocator().buffer(4);
            header.order(ByteOrder.BIG_ENDIAN);
            header.writeShort((short) pm.type);
            header.writeShort((short) 0);
            return header;
        } else { //pm.payload instanceof MessageLite
            MessageLite ml = (MessageLite) pm.payload;
            int mlsize = ml.getSerializedSize();
            byte[] res = new byte[4 + mlsize];
            // 大端序写入type，len
            res[0] = (byte) ((pm.type >> 8) & 0xFF);
            res[1] = (byte) (pm.type & 0xFF);
            res[2] = (byte) ((mlsize >> 8) & 0xFF);
            res[3] = (byte) (mlsize & 0xFF);
            CodedOutputStream cos = CodedOutputStream.newInstance(res, 4, mlsize);
            try {
                ml.writeTo(cos);
            } catch (IOException ex) {
                MLogger.getlogger().log(LogConsts.LOGTYPE_NETERR, StackTraceUtil.getStackTrace(ex));
            }
            cos = null;
            return wrappedBuffer(res);
        }
    }

    /**
     * Type4BytesLengthFieldProtobufEncoder
     */
    protected ByteBuf encodeProtobufMessage2(Channel channel, ProtobufMessage pm) {
        if (pm.payload == null) {
            ByteBuf header = channel.config().getAllocator().buffer(6);
            header.order(ByteOrder.BIG_ENDIAN);
            header.writeShort((short) pm.type);
            header.writeInt(0);
            return header;
        } else if (pm.payload instanceof byte[]) {
            ByteBuf header = channel.config().getAllocator().buffer(6);
            header.order(ByteOrder.BIG_ENDIAN);
            byte[] arr = (byte[]) pm.payload;
            header.writeShort((short) pm.type);
            header.writeInt(arr.length);
            return wrappedBuffer(header, wrappedBuffer(arr));
        } else {  //pm.payload instanceof MessageLite
            MessageLite ml = (MessageLite) pm.payload;
            int mlsize = ml.getSerializedSize();
            byte[] res = new byte[6 + mlsize];
            // 大端序写入type，len
            res[0] = (byte) ((pm.type >> 8) & 0xFF);
            res[1] = (byte) (pm.type & 0xFF);
            res[2] = (byte) ((mlsize >> 24) & 0xFF);
            res[3] = (byte) ((mlsize >> 16) & 0xFF);
            res[4] = (byte) ((mlsize >> 8) & 0xFF);
            res[5] = (byte) (mlsize & 0xFF);
            CodedOutputStream cos = CodedOutputStream.newInstance(res, 6, mlsize);
            try {
                ml.writeTo(cos);
            } catch (IOException ex) {
                MLogger.getlogger().log(LogConsts.LOGLEVEL_DEBUG, StackTraceUtil.getStackTrace(ex));
            }
            cos = null;
            return wrappedBuffer(res);
        }
    }

}
